package fun.util.daohelper;

import fun.util.daohelper.expression.SearchExpression;
import fun.util.daohelper.hibernate.ExpressionTransformerFactory;
import fun.util.daohelper.hibernate.expression.ExpressionCriterionTransformer;
import org.apache.log4j.Logger;
import org.hibernate.*;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Projections;
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;

import java.io.Serializable;
import java.util.List;


/**
 * Author: bin
 * Date: Jan 9, 2006
 * Time: 10:27:55 PM
 */
public class SpringHibernateDaoHelper <D> implements DaoHelper<D> {

    private final static Logger logger = Logger.getLogger(SpringHibernateDaoHelper.class);

    private SessionFactory sessionFactory;

    private int batchSize = 20;
    private ExpressionTransformerFactory transformerFactory;
    private HibernateTemplate hTemplate;


    public SpringHibernateDaoHelper(SessionFactory sessionFactory) {
        setSessionFactory(sessionFactory);
        transformerFactory = ExpressionTransformerFactory.INSTANCE;

        hTemplate = new HibernateTemplate(this.sessionFactory);
    }

    public void setBatchSize( int batchSize ) {
        this.batchSize = batchSize;
    }

    public void setExceptionTranslator( SQLErrorCodeSQLExceptionTranslator translator ) {
        hTemplate.setJdbcExceptionTranslator( translator );
    }

    /**
     *
     * @param flushMode - see HibernateAccessor.FLUSH_AUTO, FLUSH_COMMIT, FLUSH_EAGER, FLUSH_NEVER
     */
    public void setFlushMode( int flushMode ) {
        hTemplate.setFlushMode( flushMode );
    }

    public void setFlushMode( String flushModeName ) {
        hTemplate.setFlushModeName( flushModeName );
    }


    public D find(final Class<? extends D> type, final Serializable key) {

        return (D) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                return session.get(type, key );
            }
        });
    }

    /*
    public D save(final D d) {
        logger.debug("saving: : " + d);
        HibernateTemplate ht = new HibernateTemplate(this.sessionFactory);
        return (D) ht.execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                session.saveOrUpdate(d);
                session.flush();
                session.clear();
                return d;
            }
        });
    }
    */

    public void save(final D... objectsToSave ) {

        getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                int count=0;
                for( D obj : objectsToSave ) {
                    session.saveOrUpdate( obj );
                    if ( ++count % batchSize == 0 ) {
                        session.flush();
                        session.clear();
                    }
                }
                session.flush();
                session.clear();

                logger.debug( "saved " + count );
                return count;
            }
        });
    }


    public <F extends D> List<F> findAll( final Class<F> dataType ) {
        logger.debug("findAll: : " + dataType);

        return (List<F>) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Query query = session.createQuery("from " + dataType.getName());
                return query.list();
            }
        });
    }

    public List<D> findByFields(final Class<? extends D> dataType, final SearchExpression... expressions) {
        logger.debug("findByFields: : " + dataType);

        return (List<D>) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Criteria criteria = session.createCriteria(dataType);
                for ( SearchExpression param : expressions ) {
                    ExpressionCriterionTransformer transformer = transformerFactory.getTransformer( param.getClass() );
                    Criterion criterion = transformer.transform( param );
                    criteria.add( criterion );
                }
                List result = criteria.list();
                return result;
            }
        });
    }

    public D findUniqueByFields(Class<? extends D> dataType, SearchExpression... expressions ) {
        List<D> results = findByFields(dataType, expressions );
        if (results.size() == 0) {
            return null;
        } else if (results.size() == 1) {
            return results.iterator().next();
        } else {
            throw new IllegalStateException(results.size() + " results found!");
        }
    }

    public int countByFields( final Class<? extends D> dataType, final SearchExpression... expressions) {
        logger.debug("countByFields: : " + dataType);

        return (Integer) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Criteria criteria = session.createCriteria(dataType);
                for ( SearchExpression param : expressions ) {
                    ExpressionCriterionTransformer transformer = transformerFactory.getTransformer( param.getClass() );
                    Criterion criterion = transformer.transform( param );
                    criteria.add( criterion );
                }
                List list = criteria.setProjection(Projections.rowCount() ).list();
                return list.iterator().next();
            }
        });
    }



    public void remove( final D data ) {
        logger.debug("remove: " + data );


        getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                session.delete( data );
                session.flush();
                return data;
            }
        });
    }

    public int removeByFields( final Class<? extends D> dataType, final SearchExpression... expressions) {
        logger.debug("removeByFields: : " + dataType);

        return (Integer)getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Criteria criteria = session.createCriteria(dataType);
                for ( SearchExpression param : expressions ) {
                    ExpressionCriterionTransformer transformer = transformerFactory.getTransformer( param.getClass() );
                    Criterion criterion = transformer.transform( param );
                    criteria.add( criterion );
                }
                int count = 0;
                List result = criteria.list();
                for( Object obj : result ) {
                    session.delete( obj );
                    count++;
                }
                session.flush();
                return count;
            }
        });
    }

    public D removeById( final Class<? extends D> dataType, final Serializable key ) {
        logger.debug("remove: " + key );


        return (D) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Object obj = session.get( dataType, key );
                if ( obj!= null ) {
                    session.delete( obj );
                    session.flush();
                }
                else {
                    throw new IllegalArgumentException( "object does not exist: " + key );
                }
                return obj;
            }
        });
    }

    public int removeAll( final Class<? extends D> type) {

        return (Integer)getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Query query = session.createQuery( "delete from " + type.getName() );
                int result = query.executeUpdate();
                session.flush();
                return result;
            }
        });
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }


    protected HibernateTemplate getHibernateTemplate() {
        return hTemplate;
    }

}
